Prerequisites
Load required packages
library(tidyverse)
library(dplyr)
library(ggplot2)
library(rtweet)
library(readr)
library(DataExplorer)
Dataset
Import processed data, which can be found here.
#read preprocessed data
wines <- read.csv(file = '../data/processed_data/wines.csv')
Get sample of dataset
#set seed value to birthday of Ricardo Rodriguez, American wrestler and ring announcer and Dr. Reinaldo (Rei) Sanchez-Arias
set.seed(19630217)
#set percentage to test with for simplicity, if needed
percentage <- 5
wine_sample<- sample_n(wines, percentage/100*nrow(wines))
Split Taster data into different Data Frame
tasters <- wines %>%
select(taster_name, taster_twitter_handle) %>% unique()
tasters
Drop taster_twitter_handle in wines dataframe
wines <- wines %>%
select(-taster_twitter_handle)
head(wines)
Add Reviewer profile info
Each reviewer has there own bias. To offset that we made a “profile” for each reviewer which includes characteristics like: avg_points, sd_points, and var_points
taster_rating_profile <- wines %>%
group_by(taster_name) %>%
summarize(
avg_points = mean(points),
sd_points = sd(points),
var_points = var(points),
reviews = n()
)
tasters <- inner_join(tasters, taster_rating_profile, by = "taster_name")
head(tasters)
Add Rating Classification
Add following classification to wine dataset as found on the website:
| Classic |
98-100 |
The pinnacle of quality. |
| Superb |
94-97 |
A great achievement. |
| Excellent |
90-93 |
Highly recommended. |
| Very Good |
87-89 |
Often good value; well recommended. |
| Good |
83-86 |
Suitable for everyday consumption; often good value. |
| Acceptable |
80-82 |
Can be employed in casual, less-critical circumstances |
# function to add rating
rating_category <- function(points){
if(points>=98){
return("Classic")
}
else if (points>=94){
return("Superb")
}
else if(points>=90){
return("Excellent")
}
else if(points>=87){
return("Very Good")
}
else if(points>=83){
return("Good")
}
else{
return("Acceptable")
}
}
wines<- wines %>%
rowwise() %>%
mutate(rating_category = rating_category(points))
head(wines)
Add Adjusted Points
Since, each reviewer has a different bias we created a normalized metric, norm_points, by looking at the number of standard deviatioins a wine is from the reviewer’s avg_points. This gives use a more accurate representation of which which wines are better than the rest.
normalize_points <- function(data){
left_join(data, tasters, by = "taster_name")%>%
rowwise() %>%
mutate(norm_points = (points-avg_points)/sd_points) %>%
select(-avg_points, -sd_points, -var_points, -taster_twitter_handle, -reviews)
}
wines <- normalize_points(wines)
head(wines)
Data Sanitation
Vintage seems to have year 7200
wines <- wines %>%
filter(vintage<2019)
Data Exploration
Univariate Exploration
Correlation price by points, using DataExplorer library which can be found here
# TODO: IZZY
Alcohol Amount
# TODO: IZZY
Vintage
Count wines per year (Note: Data has been sanitized)
wines %>%
group_by(vintage) %>%
summarize(count = n())
Grouping rowwise data frame strips rowwise nature
wines %>%
ggplot() +
geom_bar(mapping = aes(x=vintage))

Winery
Count the number of wines per winery (in a column graph)
wines %>%
group_by(winery) %>%
summarize(count = n()) %>%
arrange(desc(count)) %>%
slice(1:15) %>%
ggplot() +
geom_col(mapping = aes(x=count, y =winery))
Province
Count the number of wines per province (in a column graph)
wines %>%
group_by(province) %>%
Price
(graphs to understand where the majority of the data is. (in a column graph))
mean_price <- (mean(wines$price, na.rm = TRUE))
sd_price <- (sd(wines$price, na.rm = TRUE))
min_price <- (min(wines$price, na.rm = TRUE))
max_price <- print(max(wines$price, na.rm = TRUE))
[1] 3300
ggplot(mapping = aes(mean_price, sd_price, min_price, max_price))+
geom_boxplot()
Duplicated aesthetics after name standardisation:

Points
(graphs to understand where the majority of the data is. (in a column graph))
print(mean(wines$points))
print(sd(wines$points))
print(min(wines$points))
print(max(wines$points))
Points distribution by Reviewer
wines %>%
ggplot() +
geom_boxplot(aes(y=taster_name, x=points)) +
geom_vline(xintercept = mean(wines$points))
Multivariate Exploration
Price by Points
Notice the data is “stacked” and the socres range from 80-100
wines %>%
ggplot() +
geom_point(mapping = (aes(x = points, y = price)), na.rm = T, alpha = 0.15) +
labs(title = "Price by Points", x = "Points", y = "Price")
TODO: IZZY (Why did we log this?)
wines %>%
ggplot() +
geom_point(mapping = (aes(x = points, y = log(price))), na.rm = T, alpha = 0.15) +
labs(title = "log(Price) by Points", x = "Points", y = "log(Price)")
Data Analysis
#Find the best province for wine using the average points across the 1,000 samples #drop the descriptions or just select price? set points to max(points)
mean_points <- mean(wine_sample$points)
mean_points
best_province <- wine_sample %>%
group_by(points) %>%
filter(points > mean_points) %>%
arrange(desc(points))
best_province
Best wine, by variety
#wine_best_variety <-
wines %>%
group_by(variety) %>%
summarise(mean_points = mean(points)) %>%
arrange(desc(mean_points))
user_price <- readline(prompt = "How much are you willing to spend on a bottle?")
user_price <- as.integer(user_price)
wines %>%
filter(price <= user_price) %>%
arrange(desc(points)) %>%
select(title, price, points)
Conclusion
LS0tDQp0aXRsZTogIkV4cGxvcmluZyBhbmQgQW5hbHlpemluZyBXaW5lIEVudGh1c2lhc3QgUmV2aWV3cyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgUHJlcmVxdWlzaXRlcw0KDQpMb2FkIHJlcXVpcmVkIHBhY2thZ2VzDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocnR3ZWV0KQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoRGF0YUV4cGxvcmVyKQ0KYGBgDQoNCiMgRGF0YXNldA0KDQpJbXBvcnQgcHJvY2Vzc2VkIGRhdGEsIHdoaWNoIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL0M0cmJ5bjNtNG4vd2luZV9yZXZpZXdzX2RhdGFfYW5hbHlzaXMvYmxvYi9tYXN0ZXIvZGF0YS9wcm9jZXNzZWRfZGF0YS9wcmVwcm9jZXNzaW5nLnJtZCkuDQoNCmBgYHtyfQ0KI3JlYWQgcHJlcHJvY2Vzc2VkIGRhdGENCndpbmVzIDwtIHJlYWQuY3N2KGZpbGUgPSAnLi4vZGF0YS9wcm9jZXNzZWRfZGF0YS93aW5lcy5jc3YnKQ0KYGBgDQoNCkdldCBzYW1wbGUgb2YgZGF0YXNldA0KYGBge3J9DQojc2V0IHNlZWQgdmFsdWUgdG8gYmlydGhkYXkgb2YgUmljYXJkbyBSb2RyaWd1ZXosIEFtZXJpY2FuIHdyZXN0bGVyIGFuZCByaW5nIGFubm91bmNlciBhbmQgRHIuIFJlaW5hbGRvIChSZWkpIFNhbmNoZXotQXJpYXMNCnNldC5zZWVkKDE5NjMwMjE3KQ0KDQojc2V0IHBlcmNlbnRhZ2UgdG8gdGVzdCB3aXRoIGZvciBzaW1wbGljaXR5LCBpZiBuZWVkZWQNCnBlcmNlbnRhZ2UgPC0gNQ0Kd2luZV9zYW1wbGU8LSBzYW1wbGVfbih3aW5lcywgcGVyY2VudGFnZS8xMDAqbnJvdyh3aW5lcykpDQpgYGANCg0KIyMjIFNwbGl0IFRhc3RlciBkYXRhIGludG8gZGlmZmVyZW50IERhdGEgRnJhbWUNCg0KYGBge3J9DQp0YXN0ZXJzIDwtIHdpbmVzICU+JQ0KICBzZWxlY3QodGFzdGVyX25hbWUsIHRhc3Rlcl90d2l0dGVyX2hhbmRsZSkgJT4lIHVuaXF1ZSgpDQp0YXN0ZXJzDQpgYGANCg0KRHJvcCBgdGFzdGVyX3R3aXR0ZXJfaGFuZGxlYCBpbiB3aW5lcyBkYXRhZnJhbWUNCg0KYGBge3J9DQp3aW5lcyA8LSB3aW5lcyAlPiUNCiAgc2VsZWN0KC10YXN0ZXJfdHdpdHRlcl9oYW5kbGUpDQpoZWFkKHdpbmVzKQ0KYGBgDQojIyBBZGQgUmV2aWV3ZXIgcHJvZmlsZSBpbmZvDQoNCkVhY2ggcmV2aWV3ZXIgaGFzIHRoZXJlIG93biBiaWFzLiBUbyBvZmZzZXQgdGhhdCB3ZSBtYWRlIGEgInByb2ZpbGUiIGZvciBlYWNoIHJldmlld2VyIHdoaWNoIGluY2x1ZGVzIGNoYXJhY3RlcmlzdGljcyBsaWtlOiBgYXZnX3BvaW50c2AsIGBzZF9wb2ludHNgLCBhbmQgYHZhcl9wb2ludHNgDQpgYGB7cn0NCnRhc3Rlcl9yYXRpbmdfcHJvZmlsZSA8LSB3aW5lcyAlPiUNCiAgZ3JvdXBfYnkodGFzdGVyX25hbWUpICU+JQ0KICBzdW1tYXJpemUoDQogICAgYXZnX3BvaW50cyA9IG1lYW4ocG9pbnRzKSwNCiAgICBzZF9wb2ludHMgPSBzZChwb2ludHMpLA0KICAgIHZhcl9wb2ludHMgPSB2YXIocG9pbnRzKSwNCiAgICByZXZpZXdzID0gbigpDQogICkNCg0KdGFzdGVycyA8LSBpbm5lcl9qb2luKHRhc3RlcnMsIHRhc3Rlcl9yYXRpbmdfcHJvZmlsZSwgYnkgPSAidGFzdGVyX25hbWUiKQ0KaGVhZCh0YXN0ZXJzKQ0KYGBgDQojIyMgQWRkIFJhdGluZyBDbGFzc2lmaWNhdGlvbg0KDQpBZGQgZm9sbG93aW5nIGNsYXNzaWZpY2F0aW9uIHRvIHdpbmUgZGF0YXNldCBhcyBmb3VuZCBvbiB0aGUgW3dlYnNpdGVdKGh0dHBzOi8vd3d3LndpbmVtYWcuY29tLzIwMTAvMDQvMDkveW91LWFza2VkLWhvdy1pcy1hLXdpbmVzLXNjb3JlLWRldGVybWluZWQvKToNCg0KfENhdGVnb3J5ICB8IFJhdGluZyAgfCBEZXNjcmlwdGlvbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfC0tLS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfENsYXNzaWMgICB8CTk4LTEwMCB8IFRoZSBwaW5uYWNsZSBvZiBxdWFsaXR5LiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8U3VwZXJiICAgIHwJOTQtOTcJIHwgQSBncmVhdCBhY2hpZXZlbWVudC4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnxFeGNlbGxlbnQgfAk5MC05MwkgfCBIaWdobHkgcmVjb21tZW5kZWQuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfFZlcnkgR29vZCB8ICA4Ny04OQkgfCBPZnRlbiBnb29kIHZhbHVlOyB3ZWxsIHJlY29tbWVuZGVkLiAgICAgICAgICAgICAgICAgICAgfA0KfEdvb2QJICAgICB8ICA4My04NgkgfCBTdWl0YWJsZSBmb3IgZXZlcnlkYXkgY29uc3VtcHRpb247IG9mdGVuIGdvb2QgdmFsdWUuICAgfA0KfEFjY2VwdGFibGV8CTgwLTgyCSB8IENhbiBiZSBlbXBsb3llZCBpbiBjYXN1YWwsIGxlc3MtY3JpdGljYWwgY2lyY3Vtc3RhbmNlcyB8DQoNCmBgYHtyfQ0KIyBmdW5jdGlvbiB0byBhZGQgcmF0aW5nDQpyYXRpbmdfY2F0ZWdvcnkgPC0gZnVuY3Rpb24ocG9pbnRzKXsNCiAgaWYocG9pbnRzPj05OCl7DQogICAgcmV0dXJuKCJDbGFzc2ljIikNCiAgfQ0KICBlbHNlIGlmIChwb2ludHM+PTk0KXsNCiAgICByZXR1cm4oIlN1cGVyYiIpDQogIH0NCiAgZWxzZSBpZihwb2ludHM+PTkwKXsNCiAgICByZXR1cm4oIkV4Y2VsbGVudCIpDQogIH0NCiAgZWxzZSBpZihwb2ludHM+PTg3KXsNCiAgICByZXR1cm4oIlZlcnkgR29vZCIpDQogIH0NCiAgZWxzZSBpZihwb2ludHM+PTgzKXsNCiAgICByZXR1cm4oIkdvb2QiKQ0KICB9DQogIGVsc2V7DQogICAgcmV0dXJuKCJBY2NlcHRhYmxlIikNCiAgfQ0KfQ0KDQp3aW5lczwtIHdpbmVzICU+JQ0KICByb3d3aXNlKCkgJT4lDQogIG11dGF0ZShyYXRpbmdfY2F0ZWdvcnkgPSByYXRpbmdfY2F0ZWdvcnkocG9pbnRzKSkNCmhlYWQod2luZXMpDQpgYGANCg0KIyMgQWRkIEFkanVzdGVkIFBvaW50cw0KDQpTaW5jZSwgZWFjaCByZXZpZXdlciBoYXMgYSBkaWZmZXJlbnQgYmlhcyB3ZSBjcmVhdGVkIGEgbm9ybWFsaXplZCBtZXRyaWMsIGBub3JtX3BvaW50c2AsIGJ5IGxvb2tpbmcgYXQgdGhlIG51bWJlciBvZiBzdGFuZGFyZCBkZXZpYXRpb2lucyBhIHdpbmUgaXMgZnJvbSB0aGUgcmV2aWV3ZXIncyBgYXZnX3BvaW50c2AuIFRoaXMgZ2l2ZXMgdXNlIGEgbW9yZSBhY2N1cmF0ZSByZXByZXNlbnRhdGlvbiBvZiB3aGljaCB3aGljaCB3aW5lcyBhcmUgYmV0dGVyIHRoYW4gdGhlIHJlc3QuDQoNCmBgYHtyfQ0Kbm9ybWFsaXplX3BvaW50cyA8LSBmdW5jdGlvbihkYXRhKXsNCiAgbGVmdF9qb2luKGRhdGEsIHRhc3RlcnMsIGJ5ID0gInRhc3Rlcl9uYW1lIiklPiUNCiAgICByb3d3aXNlKCkgJT4lDQogICAgbXV0YXRlKG5vcm1fcG9pbnRzID0gKHBvaW50cy1hdmdfcG9pbnRzKS9zZF9wb2ludHMpICU+JQ0KICAgIHNlbGVjdCgtYXZnX3BvaW50cywgLXNkX3BvaW50cywgLXZhcl9wb2ludHMsIC10YXN0ZXJfdHdpdHRlcl9oYW5kbGUsIC1yZXZpZXdzKQ0KfQ0KDQp3aW5lcyA8LSBub3JtYWxpemVfcG9pbnRzKHdpbmVzKQ0KaGVhZCh3aW5lcykgDQpgYGANCg0KIyMgRGF0YSBTYW5pdGF0aW9uDQoNClZpbnRhZ2Ugc2VlbXMgdG8gaGF2ZSB5ZWFyIDcyMDANCmBgYCB7cn0NCndpbmVzIDwtIHdpbmVzICU+JQ0KICBmaWx0ZXIodmludGFnZTwyMDE5KQ0KYGBgDQojIERhdGEgRXhwbG9yYXRpb24NCg0KIyMgVW5pdmFyaWF0ZSBFeHBsb3JhdGlvbg0KQ29ycmVsYXRpb24gYHByaWNlYCBieSBgcG9pbnRzYCwgdXNpbmcgYGBgRGF0YUV4cGxvcmVyYGBgIGxpYnJhcnkgd2hpY2ggY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL2RhdGFzY2llbmNlcGx1cy5jb20vYmxhemluZy1mYXN0LWVkYS1pbi1yLXdpdGgtZGF0YWV4cGxvcmVyLykNCmBgYHtyfQ0KIyBUT0RPOiBJWlpZDQpgYGANCg0KIyMjIEFsY29ob2wgQW1vdW50DQpgYGB7cn0NCiMgVE9ETzogSVpaWQ0KYGBgDQoNCiMjIyBDYXRlZ29yeQ0KYGBge3J9DQojIFRPRE86IElaWlkNCmBgYA0KDQojIyMgVmludGFnZQ0KQ291bnQgd2luZXMgcGVyIHllYXIgKE5vdGU6IERhdGEgaGFzIGJlZW4gc2FuaXRpemVkKQ0KYGBge3J9DQp3aW5lcyAlPiUNCiAgZ3JvdXBfYnkodmludGFnZSkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkNCmBgYA0KDQoNCmBgYHtyfQ0Kd2luZXMgJT4lDQogIGdncGxvdCgpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4PXZpbnRhZ2UpKQ0KYGBgDQoNCiMjIyBXaW5lcnkNCkNvdW50IHRoZSBudW1iZXIgb2Ygd2luZXMgcGVyIHdpbmVyeSAoaW4gYSBjb2x1bW4gZ3JhcGgpDQpgYGB7cn0NCndpbmVzICU+JQ0KICBncm91cF9ieSh3aW5lcnkpICU+JQ0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2MoY291bnQpKSAlPiUNCiAgc2xpY2UoMToxNSkgJT4lDQogIGdncGxvdCgpICsNCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh4PWNvdW50LCB5ID13aW5lcnkpKSANCmBgYA0KDQojIyMgUHJvdmluY2UNCkNvdW50IHRoZSBudW1iZXIgb2Ygd2luZXMgcGVyIHByb3ZpbmNlIChpbiBhIGNvbHVtbiBncmFwaCkNCmBgYHtyfQ0Kd2luZXMgJT4lIA0KICBncm91cF9ieShwcm92aW5jZSkgJT4lIA0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpICU+JSANCiAgYXJyYW5nZShkZXNjKGNvdW50KSkgJT4lIA0KICBzbGljZSgxOjEwKSAlPiUgDQogIGdncGxvdCgpKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHkgPSBwcm92aW5jZSwgeCA9IGNvdW50KSkNCmBgYA0KDQojIyMgUHJpY2UNCihncmFwaHMgdG8gdW5kZXJzdGFuZCB3aGVyZSB0aGUgbWFqb3JpdHkgb2YgdGhlIGRhdGEgaXMuIChpbiBhIGNvbHVtbiBncmFwaCkpDQpgYGB7cn0NCm1lYW5fcHJpY2UgPC0gKG1lYW4od2luZXMkcHJpY2UsIG5hLnJtID0gVFJVRSkpDQpzZF9wcmljZSA8LSAoc2Qod2luZXMkcHJpY2UsIG5hLnJtID0gVFJVRSkpDQptaW5fcHJpY2UgPC0gKG1pbih3aW5lcyRwcmljZSwgbmEucm0gPSBUUlVFKSkNCm1heF9wcmljZSA8LSBwcmludChtYXgod2luZXMkcHJpY2UsIG5hLnJtID0gVFJVRSkpDQoNCmdncGxvdChtYXBwaW5nID0gYWVzKG1lYW5fcHJpY2UsIHNkX3ByaWNlLCBtaW5fcHJpY2UsIG1heF9wcmljZSkpKw0KICBnZW9tX2JveHBsb3QoKQ0KYGBgDQoNCiMjIyBQb2ludHMgDQooZ3JhcGhzIHRvIHVuZGVyc3RhbmQgd2hlcmUgdGhlIG1ham9yaXR5IG9mIHRoZSBkYXRhIGlzLiAoaW4gYSBjb2x1bW4gZ3JhcGgpKQ0KYGBge3J9DQpwcmludChtZWFuKHdpbmVzJHBvaW50cykpDQpwcmludChzZCh3aW5lcyRwb2ludHMpKQ0KcHJpbnQobWluKHdpbmVzJHBvaW50cykpDQpwcmludChtYXgod2luZXMkcG9pbnRzKSkNCmBgYA0KDQoNClBvaW50cyBkaXN0cmlidXRpb24gYnkgUmV2aWV3ZXINCmBgYHtyfQ0Kd2luZXMgJT4lDQogIGdncGxvdCgpICsNCiAgZ2VvbV9ib3hwbG90KGFlcyh5PXRhc3Rlcl9uYW1lLCB4PXBvaW50cykpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbih3aW5lcyRwb2ludHMpKQ0KYGBgDQoNCiMjIE11bHRpdmFyaWF0ZSBFeHBsb3JhdGlvbg0KDQojIyBQcmljZSBieSBQb2ludHMNCk5vdGljZSB0aGUgZGF0YSBpcyAic3RhY2tlZCIgYW5kIHRoZSBzb2NyZXMgcmFuZ2UgZnJvbSA4MC0xMDANCmBgYHtyfQ0Kd2luZXMgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IChhZXMoeCA9IHBvaW50cywgeSA9IHByaWNlKSksIG5hLnJtID0gVCwgYWxwaGEgPSAwLjE1KSArDQogIGxhYnModGl0bGUgPSAiUHJpY2UgYnkgUG9pbnRzIiwgeCA9ICJQb2ludHMiLCB5ID0gIlByaWNlIikNCmBgYA0KDQpUT0RPOiBJWlpZIChXaHkgZGlkIHdlIGxvZyB0aGlzPykNCg0KYGBge3J9DQp3aW5lcyAlPiUgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gKGFlcyh4ID0gcG9pbnRzLCB5ID0gbG9nKHByaWNlKSkpLCBuYS5ybSA9IFQsIGFscGhhID0gMC4xNSkgKw0KICBsYWJzKHRpdGxlID0gImxvZyhQcmljZSkgYnkgUG9pbnRzIiwgeCA9ICJQb2ludHMiLCB5ID0gImxvZyhQcmljZSkiKQ0KYGBgDQoNCiMgRGF0YSBBbmFseXNpcw0KDQojRmluZCB0aGUgYmVzdCBwcm92aW5jZSBmb3Igd2luZSB1c2luZyB0aGUgYXZlcmFnZSBwb2ludHMgYWNyb3NzIHRoZSAxLDAwMCBzYW1wbGVzDQojZHJvcCB0aGUgZGVzY3JpcHRpb25zIG9yIGp1c3Qgc2VsZWN0IHByaWNlPyBzZXQgcG9pbnRzIHRvIG1heChwb2ludHMpDQpgYGB7cn0NCm1lYW5fcG9pbnRzIDwtIG1lYW4od2luZV9zYW1wbGUkcG9pbnRzKQ0KbWVhbl9wb2ludHMNCg0KYmVzdF9wcm92aW5jZSA8LSB3aW5lX3NhbXBsZSAlPiUgDQogIGdyb3VwX2J5KHBvaW50cykgJT4lIA0KICBmaWx0ZXIocG9pbnRzID4gbWVhbl9wb2ludHMpICU+JSANCiAgYXJyYW5nZShkZXNjKHBvaW50cykpDQpiZXN0X3Byb3ZpbmNlDQpgYGANCg0KDQpCZXN0IHdpbmUsIGJ5IHZhcmlldHkNCmBgYHtyfQ0KI3dpbmVfYmVzdF92YXJpZXR5IDwtIA0Kd2luZXMgJT4lIA0KICBncm91cF9ieSh2YXJpZXR5KSAlPiUgDQogIHN1bW1hcmlzZShtZWFuX3BvaW50cyA9IG1lYW4ocG9pbnRzKSkgJT4lIA0KICBhcnJhbmdlKGRlc2MobWVhbl9wb2ludHMpKSANCiAgDQpgYGANCg0KYGBge3J9DQp1c2VyX3ByaWNlIDwtIHJlYWRsaW5lKHByb21wdCA9ICJIb3cgbXVjaCBhcmUgeW91IHdpbGxpbmcgdG8gc3BlbmQgb24gYSBib3R0bGU/IikNCnVzZXJfcHJpY2UgPC0gYXMuaW50ZWdlcih1c2VyX3ByaWNlKQ0KDQp3aW5lcyAlPiUgDQogIGZpbHRlcihwcmljZSA8PSB1c2VyX3ByaWNlKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhwb2ludHMpKSAlPiUgDQogIHNlbGVjdCh0aXRsZSwgcHJpY2UsIHBvaW50cykNCmBgYA0KDQoNCiMgQ29uY2x1c2lvbg0K